package com.bolt.report.engine.fill;

import com.bolt.common.collection.IteratorUtil;
import com.bolt.common.convert.Convert;
import com.bolt.common.reflect.ObjectUtil;
import com.bolt.common.utils.StrUtil;
import com.bolt.report.definition.*;
import com.bolt.report.engine.BRDataSource;
import com.bolt.report.engine.BindData;
import com.bolt.report.engine.ObjectFactory;
import com.bolt.report.engine.ReportConstants;
import com.bolt.report.engine.model.*;
import com.bolt.report.exception.ReportException;
import com.bolt.report.util.ToolUtil;

import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;

public abstract class BaseReportFiller implements ReportFiller {

    protected ReportDefinition definition;

    protected Map<String, FillReturnValue> dataSetMap;

    protected ReportPrint reportPrint;

    protected boolean ignorePagination;

    protected float fillHeight;

    protected boolean isFirstTitleToPrint = true;

    protected boolean isFirstHeaderToPrint = true;

    protected boolean newPage = false;

    protected List<Cell> lastGroupCell;

    protected FillContext fillContext;

    protected float contextHeight = 0;

    protected PageDetails page;

    protected List<Column> columns = new ArrayList<>();

    protected AtomicInteger dataIndex = new AtomicInteger(0);

    public BaseReportFiller(ReportDefinition definition) {
        this.definition = definition;
    }

    @Override
    public ReportPrint fill(Map<String, Object> parameters, BRDataSource dataSource) throws ReportException {
        parameters.put(ReportConstants.REPORT_DATA_SOURCE, dataSource);
        return fill(parameters);
    }

    @Override
    public ReportPrint fill(Map<String, Object> parameters) throws ReportException {
        reportPrint = ObjectFactory.createReportPrint(definition);
        setParameters(parameters);
        createDataSet(parameters);
        fillContext = new FillContext(definition, dataSetMap, reportPrint, parameters);
        fillReport();
        return reportPrint;

    }

    protected void createDataSet(Map<String, Object> parameters) {
        dataSetMap = new HashMap<>();
        List<DataSetDefinition> dataSetDefinitions = definition.getDataSetDefinitions();
        Set<String> allCode = definition.getAllDataSetCode();
        for (DataSetDefinition dataSetDefinition : dataSetDefinitions) {
            if (allCode.contains(dataSetDefinition.getKey())) {
                FillDataSet dataSet = ObjectFactory.createFillDataSet(dataSetDefinition, parameters);
                dataSetMap.put(dataSetDefinition.getKey(), dataSet.initBindData());
            }
        }
    }

    protected void fillTitle() {
        isFirstTitleToPrint = false;
        if (definition.haveTitle()) {
            List<RowDefinition> rowDefinitions = definition.getTitle();
            Title title = new Title(columns, definition.isLoopPrintTitle());
            fillBand(rowDefinitions, title);
            reportPrint.setTitle(title);
        }
    }


    protected void fillColumnHeader() {
        isFirstHeaderToPrint = false;
        if (definition.haveHead()) {
            ColumnHeader columnHeader = new ColumnHeader(columns, definition.isLoopPrintHead());
            List<RowDefinition> rowDefinitions = definition.getHeads();
            fillBand(rowDefinitions, columnHeader);
            reportPrint.setColumnHeader(columnHeader);
        }
    }

    protected void fillBand(List<RowDefinition> rowDefinitions, BaseBand band) {
        for (RowDefinition rowDefinition : rowDefinitions) {
            List<Cell> cells = new ArrayList<>();
            for (CellDefinition element : rowDefinition.getCellDefinitions()) {
                Cell cell = ObjectFactory.createCell(element, fillContext, null, element.getY(), element.getX());
                cell.setStyle(element.getStyle());
                cell.setColSpan(element.getColSpan());
                cell.setRowSpan(element.getRowSpan());
                cells.add(cell);
            }
            band.appendRowCell(cells, rowDefinition.getHeight());
        }
    }

    protected int getDataSetSize(String key) {
        FillReturnValue returnValue = dataSetMap.get(key);
        if (returnValue != null) {
            return returnValue.getDataSize();
        } else {
            return 0;
        }
    }

    protected void fillSummary() {
        if (definition.haveSummary()) {
            List<RowDefinition> rowDefinitions = definition.getSummary();
            for (RowDefinition rowDefinition : rowDefinitions) {
                int rowIndex = page.getCurrentRowSize();
                List<Cell> cells = new ArrayList<>();
                for (CellDefinition element : rowDefinition.getCellDefinitions()) {
                    BindData bindData = new BindData(element.getKey(), new DataRange(0, getDataSetSize(element.getKey())));
                    Cell cell = ObjectFactory.createCell(element, fillContext, bindData, rowIndex, element.getX());
                    cell.setStyle(element.getStyle());
                    cells.add(cell);
                }
                page.appendRowCell(cells, rowDefinition.getHeight());
            }
        }
    }

    protected void setParameters(Map<String, Object> parameterValues) throws ReportException {
        setIgnorePagination(parameterValues);
        setDataSetParameter(parameterValues);
    }

    protected void setIgnorePagination(Map<String, Object> parameterValues) {
        ignorePagination = Convert.toBool(parameterValues.get(ReportConstants.IS_IGNORE_PAGINATION), definition.isIgnorePagination());
        reportPrint.setIgnorePagination(ignorePagination);
    }

    protected void setDataSetParameter(Map<String, Object> parameterValues) {
        List<DataSetDefinition> dataSetDefinitions = definition.getDataSetDefinitions();
        for (DataSetDefinition dataSetDefinition : dataSetDefinitions) {
            List<ParameterDefinition> parameterDefinitions = dataSetDefinition.getParameters();
            for (ParameterDefinition parameterDefinition : parameterDefinitions) {
                String paramName = parameterDefinition.getName();
                ParameterType paramType = parameterDefinition.getParameterType();
                if (parameterValues.containsKey(parameterDefinition.getName())) {
                    Object value = ObjectFactory.convertParameter(parameterValues.get(paramName), paramType);
                    parameterValues.put(paramName, value);
                } else {
                    Object value = ObjectFactory.convertParameter(parameterDefinition.getDefaultValue(), paramType);
                    parameterValues.put(paramName, value);
                }
            }
        }
    }


    protected boolean next(String key) throws ReportException {
        FillReturnValue fillReturnValue = dataSetMap.get(key);
        return fillReturnValue != null && fillReturnValue.next();
    }

    protected void restart(String key) {
        FillReturnValue fillReturnValue = dataSetMap.get(key);
        if (fillReturnValue != null && !fillReturnValue.isList()) {
            dataSetMap.get(key).reStart();
        }
    }

    protected Object getCurrentBindValue(String key, String expr) {
        FillReturnValue fillReturnValue = dataSetMap.get(key);
        return fillReturnValue != null ? fillReturnValue.getValue(expr) : StrUtil.EMPTY;
    }


    protected void addPage() {
        setFillHeight();
        page = new PageDetails(columns);
        reportPrint.addPage(page);
        fillContext.setPage(page);
        newPage = true;
    }

    protected void setFillHeight() {
        PaperDefinition paper = definition.getPaper();

        contextHeight = paper.getContextHeight(PaperSize.SizeUnit.PT);
        fillHeight = 0;
        if (this.isFirstTitleToPrint || definition.isLoopPrintTitle()) {
            contextHeight = contextHeight - ToolUtil.pixelToPoint(definition.getTitleHeight());
        }
        if (this.isFirstHeaderToPrint || definition.isLoopPrintHead()) {
            contextHeight = contextHeight - ToolUtil.pixelToPoint(definition.getHeadHeight());
        }
    }

    protected void fillRow(List<Cell> cells, int rowHeight) {
        page.insertCells(cells, rowHeight);
        fillHeight = fillHeight + ToolUtil.pixelToPoint(page.getLastRow().getRealHeight()) + ToolUtil.pixelToPoint(getBorderWidth(cells));
        newPage = false;
        if (fillHeight > contextHeight && !ignorePagination) {
            addPage();
        }
    }


    protected int getBorderWidth(List<Cell> cells) {
        int topWidth = 0, bottomWidth = 0, lastTopWidth = 0;

        int previousIndex = ObjectUtil.isNotNull(cells.get(0)) ? cells.get(0).getRowIndex() - 1 : 0;
        if (previousIndex < 0) previousIndex = 0;
        Row previousRow = page.getRow(previousIndex);

        if (previousRow != null) {
            Optional<Cell> cell = previousRow.getCells().stream()
                    .filter(k -> k != null && k.getStyle() != null && k.getStyle().getBottomBorder() != null && k.getStyle().getBottomBorder().getWidth() != 0)
                    .findFirst();
            if (cell.isPresent()) {
                int width = cell.get().getStyle().getBottomBorder().getWidth();
                lastTopWidth = width;
            }
        }

        for (Cell cell : cells) {
            if (ObjectUtil.isNull(cell) || ObjectUtil.isNull(cell.getStyle())) {
                continue;
            }

            BorderDefinition topBorderDef = cell.getStyle().getTopBorder();
            BorderDefinition bottomBorderDef = cell.getStyle().getBottomBorder();
            if (ObjectUtil.isNotNull(topBorderDef) && ObjectUtil.isNotNull(bottomBorderDef)) {
                continue;
            }

            topWidth = ObjectUtil.isNotNull(topBorderDef) ? topBorderDef.getWidth() : 0;
            bottomWidth = ObjectUtil.isNotNull(bottomBorderDef) ? bottomBorderDef.getWidth() : 0;
            if (topWidth != 0 && bottomWidth != 0) {
                break;
            }
        }
        return lastTopWidth > 0 ? bottomWidth : topWidth + bottomWidth;
    }

    protected void fillColumn() {
        List<Integer> columnDef = definition.getColumnDefinition();
        for (int index = 0; index < columnDef.size(); index++) {
            Column column = new Column(index);
            column.setWidth(columnDef.get(index));
            column.setIndex(index);
            columns.add(column);
        }
    }

    protected void fillStaticCell(List<Cell> cells, List<CellDefinition> cellDefinitions, int rowHeight, boolean insert) {
        if (IteratorUtil.isEmpty(cells)) {
            cells = ObjectFactory.newEmptyValueList(this.columns.size());
        }
        for (CellDefinition element : cellDefinitions) {
            Cell cell = ObjectFactory.createCell(element, fillContext, null, dataIndex.get()+element.getY(), element.getX());
            StyleDefinition style = element.getStyle();
            cell.setStyle(style);
            cell.setRowSpan(element.getRowSpan());
            cell.setColSpan(element.getColSpan());
            cells.add(element.getX(), cell);
        }
        if (insert) {
            fillRow(cells, rowHeight);
        }
    }

    protected void fillQueryForm() {

    }

    protected abstract void fillReport();
}